package distriKub;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JOptionPane;
public class Myself extends Player implements MessageListener,TimeListener{
	
	public static String playTimerName = "playTimer";
	
	// Objects needed for playing and communications
	Timer playTimer;
	GamePanel myGamePanel;
	InfoPanel myInfoPanel;
	GameListener MGL; // My Game Listener
	ChatListener MCL; // My Chat Listener
	GameComClient MGCC; // My Game Com Client
	ChatComClient MCCC; // My Chat Com Client
	DBComClient MDBCC; // My Database Client
	private int timeLeft = GamePanel.turnTime;
	private boolean isMyTurn;
	RegPlayerPanel myRegPlayerPanel; // The graphic panel for registering with the database
	InetAddress ServerAddress;
	String gameTimeSignature; // Identifier to use when dealing with the database
	// End of objects needed for playing and communications
	
	private List<Player> myOpponents = new ArrayList<Player>();
	private List<Player> deadOpponents = new ArrayList<Player>(); // Opponents who are currently unresponsive
	
	public Tile suggestedTile; // The tile to put in the additional tile in the rack

	private String _serverIpAddressString;
	
	public void updatePrivateRack(){
		for (Tile currentTile : myBank.gameBank){
			
			if (currentTile.getTileOwnerId()==this.id) {
				privateRack[currentTile.getTilePosition()] = currentTile;
			}
			if (currentTile.isSuggested()){
				suggestedTile = currentTile;
			}
		} // End of for loop
	} // End of updatePrivateRack()
	
	public void updateBankFromGUI(){ // TODO : Needs to be carefully checked (Seems to work)
		String guiTileColor = null; // TODO : When finished updating the bank we should send the bank to all the opponents
		int guiTileNumber;
		JButton[] guiRack = myGamePanel.getFinalRack();
			
		for (Tile aTile : myBank.gameBank) // Temporary, return all my tiles to the bank 
				if (aTile.getTileOwnerId()==this.id)
					aTile.setTileOwnerId(Tile.BANKID);
		
			for (int placeInGuiRack = 0; placeInGuiRack < guiRack.length; placeInGuiRack++){
				if (guiRack[placeInGuiRack].getText()=="") // The tile is a Joker
					guiTileNumber=0;
				else guiTileNumber = Integer.parseInt(guiRack[placeInGuiRack].getText());
				
				if (guiRack[placeInGuiRack].getForeground()==Color.black) guiTileColor = "Black";
				if (guiRack[placeInGuiRack].getForeground()==Color.orange) guiTileColor = "Yellow";
				if (guiRack[placeInGuiRack].getForeground()==Color.blue) guiTileColor = "Blue";
				if (guiRack[placeInGuiRack].getForeground()==Color.red) guiTileColor = "Red";
				if (guiRack[placeInGuiRack].getForeground()==Color.pink) guiTileColor = "Joker";
				
				for (Tile aTile : myBank.gameBank){
					if (aTile.getTileNumber()==guiTileNumber && aTile.getTileColor().toString().equals(guiTileColor) && aTile.getTileOwnerId()==Tile.BANKID){
						aTile.setTileOwnerId(this.getId());
						aTile.setTilePosition(placeInGuiRack);
						guiTileNumber=-10; // To not get the 2nd tile if available
					} // End of if block
				} // End of inner for loop
			}// End of outer for loop
	} // End of updateBankFromGUI()
	
	public boolean isWinning() { // Check whether I'm a legitimate winner  
		boolean iWon = true;
		for (int currentTile = 1; currentTile<13; currentTile++){
			if (privateRack[currentTile].getTileNumber()-privateRack[currentTile-1].getTileNumber()!=1) 
				iWon = false;
		} // End of for loop
		return iWon;
	} // End of isWinning()
	
	public void claimVictory(){ // This one is called when the user presses the victory button on the game panel
		if (isWinning()){ // TODO : Here goes the code for winning procedure.
			
		} // End of if block
		else {
			JOptionPane.showMessageDialog(null, "You don't have a victorious sequence of tiles.","Bad News", JOptionPane.ERROR_MESSAGE);
		} // End of else block
	} // End of claimVictory()
	
	public void addOpponents(String opponentDetailsToParse){
		String[] parsedDetails = opponentDetailsToParse.split(",");
		int playOrder = 1;
		
			for (String oppDetails : parsedDetails){
		
				// Format : id,screenName,ipAddress
				String oppId = oppDetails.split("#")[0];
				String oppScreenName = oppDetails.split("#")[1];
				String oppIpAddress = oppDetails.split("#")[2];
				
				if (oppId.equals(this.id)){
					this.setPlayOrder(playOrder++);
				}
				
				else {
					Player theOpponent = new Player();
					theOpponent.setId(Integer.parseInt(oppId));
					theOpponent.setScreenName(oppScreenName);
					theOpponent.setPlayOrder(playOrder++);
					
					try {
						theOpponent.setIpAddress(InetAddress.getByName(oppIpAddress));
						myInfoPanel.displaySystemMessage("Successfully set ip address for " + oppScreenName + " - " + theOpponent.getIpAddress());
					} catch (UnknownHostException UHEX) {
						myInfoPanel.displaySystemMessage("\n===> Myself.java ===> Error in addOpponent\n");
						UHEX.printStackTrace();
					} // End of catch block
					
					myInfoPanel.displaySystemMessage("\nDefined Opponent :\nId - " + theOpponent.getId() + "\nScreen Name - " + theOpponent.getScreenName() + 
							"\nIpAddress - " + theOpponent.getIpAddress() + "\nPlay Order - " + theOpponent.getPlayOrder());
					
					myOpponents.add(theOpponent);
					checkPulses(); // Check what opponents are actually alive
					updateGuiOpponents(); // Reflect the change on the GUI
				}
			}
	} // End of addOpponent()
	
	public void removeOpponent(int opponentID){
		for (Player theOpponent : myOpponents)
			if (theOpponent.getId()== opponentID)
				myOpponents.remove(theOpponent);
		
		for (Player theOpponent : deadOpponents)
			if (theOpponent.getId()== opponentID)
				myOpponents.remove(theOpponent);
		
		checkPulses();
		updateGuiOpponents(); // Reflect the change on the GUI
	} // End of removeOpponent()
	
	private void dealTiles(Player dealToOpponent,int howManyToDeal) { // Tested OK
		Random randomPlaceInBank = new Random(Bank.NUMBEROFTILES);
		
		int tilesDealt = 0;
		int currentPlaceInBank;
		while (tilesDealt<howManyToDeal){
			randomPlaceInBank.setSeed(System.currentTimeMillis());
			currentPlaceInBank = randomPlaceInBank.nextInt(Bank.NUMBEROFTILES);
				if (myBank.gameBank[currentPlaceInBank].getTileOwnerId()==Tile.BANKID)
				{
					myBank.gameBank[currentPlaceInBank].setTileOwnerId(dealToOpponent.getId());
					myBank.gameBank[currentPlaceInBank].setOwnerScreenName(dealToOpponent.getScreenName());
					myBank.gameBank[currentPlaceInBank].setTilePosition(tilesDealt);
					tilesDealt++;
				}// End of if statement
			}// End of While loop
		
		randomPlaceInBank.setSeed(System.currentTimeMillis());
		currentPlaceInBank = randomPlaceInBank.nextInt(Bank.NUMBEROFTILES);
		while (myBank.gameBank[currentPlaceInBank].getTileOwnerId()!=Tile.BANKID){
			currentPlaceInBank = randomPlaceInBank.nextInt(Bank.NUMBEROFTILES);
		} // End of while loop
			if (myBank.gameBank[currentPlaceInBank].getTileOwnerId()==Tile.BANKID)
			{
				myBank.gameBank[currentPlaceInBank].suggest();
			} // End of if block
	} // End of dealTiles()
	
	public void dealToAll() {// Must call setOpponents before dealing Tiles
		myBank.resetGameBank();
		dealTiles(this,13); // TODO: Handle the situation that 1st player must draw from the bank
		this.updatePrivateRack();
		for (Player currentOpponent : myOpponents)
			dealTiles(currentOpponent,13);
		
		circulateBank();
	}
	
	public void updateBank(String newBank) { // Synchronize my bank with the given one
		myBank.bankFromString(newBank);
	}
	
	public void circulateBank(){ // Distribute my bank to all my opponents
		checkPulses(); // Making sure the bank is being posted only to live opponents
		updateGuiOpponents(); // Reflect the change on the GUI
		myBank.raiseSeniority();
		for (Player theOpponent : myOpponents){
			if (!MGCC.sendGameMessage(ComRules.BankUpdate + ComRules.MessageDelimiter + myBank.toString(), theOpponent.getIpAddress(), ComRules.gamePort, ComRules.standardTimeout))
				MGCC.sendGameMessage(ComRules.BankUpdate + ComRules.MessageDelimiter + myBank.toString(), theOpponent.getIpAddress(), ComRules.gamePort, ComRules.extendedTimeout);
		} // End of for loop
	} // End of circulateBank()
	
	public void circulateGameSignature(){
		checkPulses(); // Making sure the signature is being posted only to live opponents
		updateGuiOpponents(); // Reflect the change on the GUI
		for (Player theOpponent : myOpponents){
			if (!MGCC.sendGameMessage(ComRules.GameTimeSignatureUpdate + ComRules.MessageDelimiter + gameTimeSignature, theOpponent.getIpAddress(), ComRules.gamePort, ComRules.standardTimeout))
				MGCC.sendGameMessage(ComRules.GameTimeSignatureUpdate + ComRules.MessageDelimiter + gameTimeSignature, theOpponent.getIpAddress(), ComRules.gamePort, ComRules.extendedTimeout);
		}
	} // End of circulateGameSignature()
	
	public void checkPulses(){
		List<Player> toRemove = new ArrayList<Player>();
		List<Player> toAdd = new ArrayList<Player>(); 
		for (Player liveOpponent : myOpponents){ // Processing opponents that are currently considered alive
			for (int i = 0; i<3; i++){
				if (!MCCC.sendChatMessage(ComRules.CheckPulse + ComRules.MessageDelimiter + "Nothing", liveOpponent.getIpAddress(), ComRules.standardTimeout)){
					if (!deadOpponents.contains(liveOpponent)) deadOpponents.add(liveOpponent);
					if (myOpponents.contains(liveOpponent)) //myOpponents.remove(liveOpponent);
						toRemove.add(liveOpponent);
				} else{
					if (deadOpponents.contains(liveOpponent)) //deadOpponents.remove(liveOpponent);
						toRemove.add(liveOpponent);
					if (!myOpponents.contains(liveOpponent)) //myOpponents.add(liveOpponent);
						toAdd.add(liveOpponent);
					}
			}
			
		} // End of outer for block
		myOpponents.remove(toRemove);
		myOpponents.addAll(toAdd);
		
		for (Player deadOpponent : deadOpponents){ // Processing opponents that are currently considered dead
			for (int i = 0; i<3; i++){
				if (!MCCC.sendChatMessage(ComRules.CheckPulse, deadOpponent.getIpAddress(), ComRules.standardTimeout)){
					if (myOpponents.contains(deadOpponent)) myOpponents.remove(deadOpponent);
				} else{
					if (deadOpponents.contains(deadOpponent)) deadOpponents.remove(deadOpponent);
					if (!myOpponents.contains(deadOpponent)) myOpponents.add(deadOpponent);
					}
			}
		} // End of outer for block
		
		String[] theDeadOpponents = new String[deadOpponents.size()];
		
		for (int i=0; i<deadOpponents.size(); i++)
			theDeadOpponents[i] = deadOpponents.get(i).getScreenName();
		myGamePanel.updateOpponentsInGUI(theDeadOpponents); // TODO : IS THIS A BUG ?!?!! (Should get live opponents list?)
	} // End of checkPulses()
		
	public void messageReceived(ComEvent comMessage){ // Executed each time a message is received by communication
		
		String rawMessage = comMessage.getMessage();
		String messageId = "UNINITIALIZED ID";
		myInfoPanel.displaySystemMessage("\n===> Myself.java ===> Incomming message : " + rawMessage+"\n");
		String theMessage = "Uninitialized String";
		if (rawMessage.indexOf(ComRules.MessageDelimiter)!=-1){ // The message has a classification
			messageId = rawMessage.split(ComRules.MessageDelimiter)[0];
			theMessage = rawMessage.split(ComRules.MessageDelimiter)[1];
		} else
			myInfoPanel.displaySystemMessage("\n===> Myself.java ===> messageReceived() : UNRECOGNIZED MESSAGE WAS RECEIVED !\n");
		
		if (messageId.equals(ComRules.ChatMessage)){ // Chat message
			myGamePanel.setChatMessage(theMessage);
		} // End of Chat message
		
		else if (messageId.equals(ComRules.GameTimeSignatureUpdate)){ // Game signature update
			gameTimeSignature = theMessage;
		} // End of Game signature update
		
		else if (messageId.equals(ComRules.BankUpdate)){ // Bank update
			updateBank(theMessage);
		}
		
		else if (messageId.equals(ComRules.YouAreDealer)){ // A message from the server announcing I'm the dealer
			checkPulses();
			dealToAll();
			circulateBank();
		}
		
		else if (messageId.equals(ComRules.RequestNameListFromServer)){
			String[] userNames = theMessage.split(",");
			myRegPlayerPanel.updateRegisteredUsernamesInGUI(userNames);
		}
		
		else if (messageId.equals(ComRules.LoginIncorrect))
		{
			JOptionPane.showMessageDialog(null, "Login incorrect! please try again.","Bad News", JOptionPane.ERROR_MESSAGE);
		}
		
		//login accepted and added to group
		else if (messageId.equals(ComRules.OKAddedToGroup))
		{
			JOptionPane.showMessageDialog(null, "Login successful, added to play group","Good News", JOptionPane.INFORMATION_MESSAGE);
			MGCC.sendGameMessage(ComRules.RequestPlayersListInMyGroup+ComRules.MessageDelimiter+getScreenName(), this.ServerAddress, ComRules.serverPort, ComRules.standardTimeout);
		}
		
		else if (messageId.equals(ComRules.DBOKRegistered))
		{
			JOptionPane.showMessageDialog(null, "Registered successfully, added to database.","Good News", JOptionPane.INFORMATION_MESSAGE);
		}
		
		else if (messageId.equals(ComRules.PlayerListInGroup))
		{
			this.addOpponents(theMessage);
		}
	} // End of messageReceived method
	
	@Override
	public void timeTick(TimeEvent event) { // TODO : Implement actual time listening reaction
		if (((Timer)event.getSource()).getTimerName() == playTimerName && timeLeft>0 && isMyTurn){
			timeLeft--;
			myGamePanel.setTimerValue(timeLeft);
		} // End of if block
		
	} // End of timeTick()
	
	public void updateGuiRack(){
		updatePrivateRack();
		myGamePanel.setTiles(getPrivateRack());
		myGamePanel.setAdditionalTile(suggestedTile);
		suggestedTile.unSuggest();
	} // End of updateGuiRack()
	
	public void updateGuiOpponents(){
		String[] opponentsNames = new String[myOpponents.size()];
		for (int i=0; i<myOpponents.size(); i++){
			opponentsNames[i] = myOpponents.get(i).screenName;
		}
		myGamePanel.updateOpponentsInGUI(opponentsNames);
	} // End of updateGuiOpponents()
	
	public void startMyTurn(){
		isMyTurn = true;
		playTimer = new Timer();
		playTimer.setTimerName(playTimerName);
		playTimer.addTimeListener(this);
		playTimer.start(); // TODO : Not really necessary right now
		myGamePanel.setTurnButtonAvailability(true);
		myGamePanel.setBankButtonAvailability(true);
	}
	
	public void stopThePlayTimer(){
		isMyTurn = false;
		playTimer.stopTimer();
		myGamePanel.setTurnButtonAvailability(false);
		myGamePanel.setBankButtonAvailability(false);
		myGamePanel.setTimerValue(GamePanel.turnTime);
	}
	
	public void sendChatFromGUI(String theMessage, String opponentName){
		for (Player theOpponent : myOpponents){
			if (theOpponent.getScreenName().equals(opponentName))
				if (!MCCC.sendChatMessage(ComRules.ChatMessage  + ComRules.MessageDelimiter + theMessage, theOpponent.getIpAddress(), ComRules.standardTimeout))
					if (!MCCC.sendChatMessage(ComRules.ChatMessage  + ComRules.MessageDelimiter + theMessage, theOpponent.getIpAddress(), ComRules.extendedTimeout))
						JOptionPane.showMessageDialog(null, "It seems the chat message could not be sent to " + opponentName,"Chat message was not sent", JOptionPane.ERROR_MESSAGE);
		} // End of for block
	} // End of sendChatFromGUI()
	
	public int getBankSeniority(){
		return myBank.getSeniority();
	}
	
	public Player getOpponentByInetAddress(InetAddress theAddress){ // TODO : Returns null if not opponent found
		Player desiredOpponent = null;
		for (Player theOpponent : myOpponents){
			if ((theOpponent.getIpAddress()).equals(theAddress))
				desiredOpponent = theOpponent;
		} // End of for loop
		return desiredOpponent;
	}
	
	public void closeAllCommunications(){

		// Closing TCP game listener
		MGL.closeGameListener();
		if (!MGL.getSocket().isClosed())
			try {MGL.getSocket().close();} catch (IOException e) {}
			
		// Closing UDP chat listener
		MCL.closeChatListener();
		if(!MCL.getSocket().isClosed())
			MCL.getSocket().close();

		// Closing chat and game clients
		MCCC.closeChatClient();
		MGCC.closeGameClient();
	} // End of closeAllCommunications()
	
	private boolean locateServer(String serverIpAddress){
		Boolean serverFound = false;
		MGCC = new GameComClient();
		try{
			ServerAddress = InetAddress.getByName(serverIpAddress);
			if (MGCC.sendGameMessage(ComRules.AreYouServer + ComRules.MessageDelimiter + "Nothing", ServerAddress, ComRules.serverPort, ComRules.standardTimeout))
				serverFound = true;
		} // The only listener at that port could be a server
		catch (Exception E){serverFound=false;}
		return serverFound;
	} // End of locateServer() method
	
	public Myself(){ // Constructor
	// Now we instantiate the communication and game objects
		
		_serverIpAddressString = JOptionPane.showInputDialog("Welcome to DistriKub !\nPlease enter a valid DistriKub server ip address"); // Null if canceled
		while (!locateServer(_serverIpAddressString)){ // TODO : Uncomment this block !!
			myInfoPanel.setConnectionStatus(false);
			_serverIpAddressString = JOptionPane.showInputDialog("Please enter a valid DistriKub server ip address"); // Null if canceled
			if (_serverIpAddressString==null) JOptionPane.showMessageDialog(null, "No valid server address was supplied.\nPlease try again later with a valid address.","Exiting...", JOptionPane.ERROR_MESSAGE);
			if (_serverIpAddressString==null) System.exit(0);
		} // End of while block
	
		myInfoPanel = new InfoPanel();
		
		myInfoPanel.setConnectionStatus(true);
		
		//send login message
		
		
		MGL = new GameListener(ComRules.gamePort);
		MGL.addMessageListener(this);
		MGL.start();
		MCL = new ChatListener();
		MCL.addMessageListener(this);
		MCL.start();
		
		
		MCCC = new ChatComClient();
		
		MDBCC = new DBComClient(ServerAddress);
	
	// Initializing the bank I'm holding
	myBank.resetGameBank();
	
	// Instantiating my game panel
	myGamePanel = new GamePanel();
	myGamePanel.bindToPlayer(this);
	myGamePanel.setVisible(true);
	        
	// Disabling the action buttons until self turn arrives
	myGamePanel.setTurnButtonAvailability(false);
	myGamePanel.setBankButtonAvailability(false);
	
	myRegPlayerPanel = new RegPlayerPanel();
	myRegPlayerPanel.bindToPlayer(this);

	// Start panel animation
	
	//Get the screen size  
/*	Toolkit toolkit = Toolkit.getDefaultToolkit();  
	Dimension screenSize = toolkit.getScreenSize();
	
	int screenX = screenSize.width;
	int screenY = screenSize.height; 
	
	for (int y = 0; y<screenY*0.2; y++){
		myGamePanel.setLocation((int)(screenX*0.2), y);
		try {
			Thread.sleep(3);
		} catch (InterruptedException e) {
			myInfoPanel.displaySystemMessage("\n===> Myself.java ===> Error in panel animation. (Constructor) \n");
			e.printStackTrace();
		}
	}*/
	// End of panel animation
	
	// Setting self IP Address
	try {
		this.setIpAddress(InetAddress.getLocalHost());
		System.out.println("\n===> Myself.java ===> I've set ipAddress to : " + getIpAddress().toString() + " (Constructor) \n");
	} catch (Exception Exp){
		System.out.println("\n===> Myself.java ===> Error : IP address for the local host could not be found. (Constructor) \n");
		System.exit(1);
		} //End of setting self IP Address
	
	myInfoPanel.bindToPlayer(this);
	myInfoPanel.bindToRegPlayerPanel(myRegPlayerPanel);
	myInfoPanel.setLocation(myGamePanel.getSize().width/2, myGamePanel.getLocation().y + myGamePanel.getSize().height + 20);
	myInfoPanel.setVisible(true);
	myInfoPanel.setIpAddress(this.ipAddress.toString().substring(this.ipAddress.toString().indexOf('/')+1, this.ipAddress.toString().length()));
	myInfoPanel.setGamePort(""+ComRules.gamePort);
	myInfoPanel.setChatPort(""+ComRules.chatPort);
	myInfoPanel.setServerIpAddress(_serverIpAddressString);
	myGamePanel.bindToInfoPanel(myInfoPanel);
	
	} // End of Myself constructor

	public void Login(String serverIpAddress, String screenName, String password) {
		MGCC.sendGameMessage(ComRules.AddMeToGroup + ComRules.MessageDelimiter + screenName+ "," +password, ServerAddress, ComRules.serverPort, ComRules.standardTimeout);
	}
	
	public void startGame()
	{
		InetAddress inetAddrs;
		try {
			inetAddrs = InetAddress.getByName(_serverIpAddressString);
			if (!MGCC.sendGameMessage(ComRules.IWannaStart  + ComRules.MessageDelimiter , inetAddrs, ComRules.serverPort, ComRules.standardTimeout))
				if (!MGCC.sendGameMessage(ComRules.IWannaStart  + ComRules.MessageDelimiter , inetAddrs, ComRules.serverPort, ComRules.standardTimeout));
					//JOptionPane.showMessageDialog(null, "It seems the chat message could not be sent to " + opponentName,"Chat message was not sent", JOptionPane.ERROR_MESSAGE);
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	public void registerUser(String userId, String screenName, String password)
	{
		InetAddress serverIpAddress;
		try {
			serverIpAddress = InetAddress.getByName(_serverIpAddressString);
			if (!MGCC.sendGameMessage(ComRules.RegisterNewPlayer + ComRules.MessageDelimiter + userId + "," + screenName + "," + password, serverIpAddress, ComRules.serverPort, ComRules.standardTimeout))
				if (!MGCC.sendGameMessage(ComRules.RegisterNewPlayer + ComRules.MessageDelimiter + userId + "," + screenName + "," + password, serverIpAddress, ComRules.serverPort, ComRules.standardTimeout));
			
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
